/*
 * CategoryClassificationDoubleCheck is a software package that uses 
 * data mining to verify that descriptions of items are classified 
 * into the correct categories.
 * 
 * Copyright (C) 2011  CommuniPatient, LLC
 * 
 * File: ClassificationValidation.java
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.unitvectory.categoryclassification;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import weka.classifiers.bayes.NaiveBayes;
import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.converters.TextDirectoryLoader;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.StringToWordVector;

/**
 * Performs classification using WEKA.
 * 
 * @author Jared Hatfield
 * 
 */
public class ClassificationValidation {

	/**
	 * Runs the classification validation on the contents of the specified
	 * folder.
	 * 
	 * <br />
	 * <br />
	 * 
	 * The positive and negative weights are designed to be used to reduce the
	 * confidence of the non-classified items. The design is such that a large
	 * positive value and a small negative value will provide more suggestions
	 * because the possibility of a misclassified item increases.
	 * 
	 * @param loc
	 *            The location of the folder.
	 * @param positive
	 *            The weight assigned to classified items.
	 * @param negative
	 *            The weight assigned to non-classified items.
	 * @return The results.
	 */
	public static List<String> run(File loc, double positive, double negative) {
		List<String> misclassified = new ArrayList<String>();
		try {
			// Convert the directory into a dataset
			TextDirectoryLoader loader = new TextDirectoryLoader();
			loader.setDirectory(loc);
			loader.setOutputFilename(true);
			Instances dataRaw = loader.getDataSet();

			// Apply the StringToWordVector
			StringToWordVector filter = new StringToWordVector();
			filter.setInputFormat(dataRaw);
			Instances dataFiltered = Filter.useFilter(dataRaw, filter);

			// Apply the specified weights to the data
			for (int i = 0; i < dataFiltered.numInstances(); i++) {
				Instance inst = dataFiltered.instance(i);
				Attribute att = inst.classAttribute();
				String cla = att.value((int) inst.classValue());
				if (cla.equals("in")) {
					// The item was classified
					inst.setWeight(positive);
				} else {
					// The item was not classified
					inst.setWeight(negative);
				}
			}

			// Train the NaiveBayes model
			NaiveBayes classifier = new NaiveBayes();
			classifier.buildClassifier(dataFiltered);

			for (int i = 0; i < dataFiltered.numInstances(); i++) {
				Instance inst = dataFiltered.instance(i);

				double classified = classifier.classifyInstance(inst);
				double actual = inst.classValue();

				if (classified != actual) {
					Instance stringInstance = dataRaw.instance(i);
					double[] nums = classifier.distributionForInstance(inst);

					// Save the filename for the misclassified element
					for (int j = 0; j < stringInstance.numAttributes(); j++) {
						Attribute att = stringInstance.attribute(j);
						if (att.name().equals("filename") && att.isString()) {
							String val = stringInstance.stringValue(att);
							val += ":" + nums[(int) classified];
							misclassified.add(val);
							break;
						}
					}
				}
			}
		} catch (Exception e) {
			System.err.println(e.getMessage());
		}

		return misclassified;
	}
}
